package com.foreveross.crawl.adapter.sub.impl20140402.v3.xiechen;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.message.BasicNameValuePair;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.foreveross.crawl.adapter.PlaneInfoEntityBuilder;
import com.foreveross.crawl.adapter.sub.impl20140402.v3.XieChenAdapter;
import com.foreveross.crawl.common.exception.self.SourceDataParseNullException;
import com.foreveross.crawl.common.exception.self.SourceWebPageNullException;
import com.foreveross.crawl.common.util.DateUtil;
import com.foreveross.crawl.common.util.RegHtmlUtil;
import com.foreveross.crawl.domain.airfreight.CabinEntity;
import com.foreveross.crawl.domain.airfreight.StopOverEntity;
import com.foreveross.crawl.domain.airfreight.TransitEntity;
import com.foreveross.crawl.domain.airfreight.doub.DoublePlaneInfoEntity;
import com.foreveross.crawl.domain.airfreight.doub.ReturnCabinEntity;
import com.foreveross.crawl.domain.airfreight.doub.ReturnDoublePlaneInfoEntity;
import com.foreveross.crawl.domain.airfreight.doub.ReturnStopOverEntity;
import com.foreveross.crawl.domain.airfreight.doub.ReturnTransitEntity;
import com.foreveross.crawl.exception.FlightInfoNotFoundException;
import com.foreveross.taskservice.common.bean.TaskModel;

/**
 * 携程国际往返程
 * 
 * @author xiangsf 2014-07-18
 * @deprecated xiangsf 2014-10-09 改版使用<code>XieChenInterRoundTrip</code>
 */
public class XieChenInternationRoundTrip {
	protected Log logger = LogFactory.getLog(getClass());
	private final static boolean IS_LOGGER_ACTION = false;// 是否记录执行动作

	private static int secondLayer_fail_retry_times = 3;// 第二层级抓取失败时重试次数
	private final static int thirdLayer_fail_retry_times = 1;// 第三层级抓取失败时重试次数
	private final static int moreCabin_fail_retry_times = 1;// 除经济舱外其它更多舱位（商务，头等）抓取失败时重试次数
	private final static int pageno_fail_retry_times = 1;// 分页抓取失败时重试次数

	private final static boolean is_secondLayer_fetch = true;// 是否抓取第二层级内容
	private final static boolean is_thirdLayer_fetch = false;// 是否抓取第三层级内容, 第三层需要登陆，不开启
	private final static boolean is_moreCabin_fetch = false;// 是否抓取更多舱位内容
	private final static boolean is_pageno_fetch = false;// 是否抓取分页数据

	private final static boolean is_no_stop = true; // 中转信息过滤:true 直飞

	private TaskModel taskQueue;

	private XieChenAdapter adapter;

	private HttpClient httpClient;

	private Map<String, String> queryParams = new HashMap<String, String>();
	private Map<String, String> cabinParams = new HashMap<String, String>();

	public XieChenInternationRoundTrip(XieChenAdapter adapter, TaskModel taskQueue) {
		this.adapter = adapter;
		this.taskQueue = taskQueue;
		if (adapter.isUseProxyip()) secondLayer_fail_retry_times = 8; // 如果使用代理IP则重试多几次。
	}

	public List<Object> fetchData() throws Exception {
		List<Object> results = new ArrayList<Object>();

		try {
			cabinParams.put("Y", "经济舱");
			cabinParams.put("C", "商务舱");
			cabinParams.put("F", "头等舱");
			// 根据三个舱位信息请求三次,经济Y，商务C，头等F ,如果三个舱位信息都没有航班数据，则返回没有航班异常
			// 1、根据查询条件直接进入到去程航班列表(默认是经济舱的,代码是Y)
			logger.info("获得经济舱Y的数据");
			this.httpClient = adapter.getHttpClient();
			List<DoublePlaneInfoEntity> yList = this.fetchSingleCabinType("Y");// 经济Y
			results.addAll(yList);
			if (is_moreCabin_fetch) { // 统一控制是否获得更多舱位数据
				// 2、取商务舱，代码是C
				// 2.1、重复上面,1里的1,2,3,4
				for (int i = 0; i < moreCabin_fail_retry_times; i++) {
					Thread.sleep(2000);
					try {
						logger.info("获得商务舱C的数据");
						results.addAll(this.fetchSingleCabinType("C"));
						break;
					} catch (Exception e) {
						if (e instanceof FlightInfoNotFoundException) break;
						logger.error(e.getMessage());
						adapter.switchProxyipByHttClient(); // 切换IP
					} finally {

					}
				}

				// 3、取头等舱，代码是F
				// 3.1、重复上面,1里的1,2,3,4
				for (int i = 0; i < moreCabin_fail_retry_times; i++) {
					Thread.sleep(2000);
					try {
						logger.info("获得头等舱F的数据");
						results.addAll(this.fetchSingleCabinType("F"));
						break;
					} catch (Exception e) {
						if (e instanceof FlightInfoNotFoundException) break;
						logger.error(e.getMessage());
						adapter.switchProxyipByHttClient(); // 切换IP
					} finally {

					}
				}
			}
		} finally {
			cabinParams = null;
			queryParams = null;
		}
		// 不进行合并结果集了(同一个航班进行返程舱位的合并)
		return results;
	}

	/**
	 * 组装去程中转数据
	 * 
	 * @param table
	 * @return
	 * @throws Exception
	 */
	private List<TransitEntity> parseGoTrantsInfo(Element table) throws Exception {
		List<TransitEntity> transits = new ArrayList<TransitEntity>();
		Element planeEle = table.select("tr.result_subinfo").first(); // 航班列表
		Elements transPlanes = planeEle.select("div.airlines"); // 航班明细，多个时为有中转的
		Elements stopEles = planeEle.select("div.stopover_line"); // 中转停留时间,包括多次中转
		Element e1 = null;
		Element stop1 = null;
		String s1 = null;
		String s2 = null;
		Elements portEles = null;
		String tFlightNo = null;
		String tCarrierName = null;
		String tFlightType = null;
		String startTime = null;
		String endTime = null;
		String tFromAirPort = null;
		String tFromAirPortName = null;
		String tToAirPort = null;
		String tToAirPortName = null;
		String stayTime = null;
		TransitEntity transitEntity = null;
		try {
			logger.info(String.format("获得的去程中转航班数量：%s", transPlanes.size()));
			for (int i = 0; i < transPlanes.size(); i++) { // 循环保存多个
				e1 = transPlanes.get(i);
				s1 = e1.getElementsByTag("dt").first().ownText().trim();
				s2 = e1.getElementsByTag("dt").first().getElementsByTag("a").first().ownText().trim();
				portEles = e1.getElementsByTag("dd");
				tFlightNo = RegHtmlUtil.regStr(s1, ".*?(\\p{Upper}{2}\\d{1,5}).*?類型");// 航班号
				tCarrierName = RegHtmlUtil.regStr(s1, "(.*)\\p{Upper}{2}\\d{1,5}.*?類型"); // 解析出航空公司名称
				tFlightType = RegHtmlUtil.regStr(s1, "類型：(.*)") + s2;// 机型
				// 可能会有经停的，会在两个dd中间
				startTime = RegHtmlUtil.regStr(portEles.get(0).ownText(), "週.*(\\d{2}.*月.*\\d{4}.*\\d{2}:\\d{2}).*\\p{Upper}{3}");// 起飞时间
				endTime = RegHtmlUtil.regStr(portEles.get(portEles.size() - 1).ownText(), "週.*(\\d{2}.*月.*\\d{4}.*\\d{2}:\\d{2}).*\\p{Upper}{3}");// 到达时间
				tFromAirPort = RegHtmlUtil.regStr(portEles.get(0).ownText(), "\\d{2}:\\d{2}.*(\\p{Upper}{3})");// 出发机场三字码
				tFromAirPortName = RegHtmlUtil.regStr(portEles.get(0).ownText(), "\\d{2}:\\d{2}.*\\p{Upper}{3}\\s*([^\\w]*)\\s*");// 出发机场名称
				tToAirPort = RegHtmlUtil.regStr(portEles.get(portEles.size() - 1).ownText(), "\\d{2}:\\d{2}.*(\\p{Upper}{3})");// 到达机场三字码
				tToAirPortName = RegHtmlUtil.regStr(portEles.get(portEles.size() - 1).ownText(), "\\d{2}:\\d{2}.*\\p{Upper}{3}\\s*([^\\w]*)\\s*");// 到达机场名称
				transitEntity = PlaneInfoEntityBuilder.buildTransitEntity(tFlightNo, null, null, tCarrierName, tCarrierName, tFromAirPort, tFromAirPortName, tToAirPort, tToAirPortName, tFlightType);
				transitEntity.setStartTime(XieChenFormatDataUtils.formatCNDate(startTime));
				transitEntity.setEndTime(XieChenFormatDataUtils.formatCNDate(endTime));
				if (i < transPlanes.size() - 1) { // 只在前面的中转对象里存放停留时长
					stop1 = stopEles.get(i);
					stayTime = RegHtmlUtil.regStr(stop1.getElementsByTag("p").first().ownText(), ".*\\((\\d{1,2}h:\\d{1,2}m)\\).*");
					transitEntity.setStayTime(XieChenFormatDataUtils.formatTimeInterval(stayTime));
				}
				transits.add(transitEntity);
			}
		} finally {
			planeEle = null;
			transPlanes = null;
			stopEles = null;
			e1 = null;
			stop1 = null;
			s1 = null;
			s2 = null;
			portEles = null;
			tFlightNo = null;
			tCarrierName = null;
			tFlightType = null;
			startTime = null;
			endTime = null;
			tFromAirPort = null;
			tFromAirPortName = null;
			tToAirPort = null;
			tToAirPortName = null;
			stayTime = null;
			transitEntity = null;
		}
		return transits;
	}

	/**
	 * 经停,从中转中解析出来
	 * 
	 * @param tFlightNo
	 * @param portEles
	 * @return
	 */
	private List<StopOverEntity> parseStopOver(Element table) {
		List<StopOverEntity> stopOvers = new ArrayList<StopOverEntity>();
		Element planeEle = table.select("tr.result_subinfo").first(); // 航班列表
		Elements stopPlanes = planeEle.select("dd.airline_stops"); // 航班明细，多个时为有中转的

		if (stopPlanes == null || stopPlanes.isEmpty()) return stopOvers;

		Elements transPlanes = planeEle.select("div.airlines"); // 航班明细，多个时为有中转的
		Element e1 = null;
		String s1 = null;
		Elements portEles = null;
		String tFlightNo = null;
		StopOverEntity so = null;
		try {
			for (int i = 0; i < transPlanes.size(); i++) { // 循环保存多个
				e1 = transPlanes.get(i);
				s1 = e1.getElementsByTag("dt").first().ownText().trim();
				portEles = e1.getElementsByTag("dd");
				tFlightNo = RegHtmlUtil.regStr(s1, "公司.*?(\\p{Upper}{2}\\d{1,5})");
				for (Element e : portEles) {
					if (e.hasClass("airline_stops")) {// 是经停的
						so = new StopOverEntity();
						so.setFlightNo(tFlightNo);
						so.setStopCity(RegHtmlUtil.regStr(e.ownText(), "經停/中轉:  (.*)Stay time").trim());
						so.setStayTime(XieChenFormatDataUtils.formatTimeInterval(RegHtmlUtil.regStr(e.ownText(), "Stay time :  \\((\\d{1,2}h\\d{1,2}m)\\)")));
						stopOvers.add(so);
					}
				}
			}
		} finally {
			stopPlanes = null;
			e1 = null;
			s1 = null;
			portEles = null;
			tFlightNo = null;
			so = null;
			transPlanes = null;
			planeEle = null;
		}
		return stopOvers;
	}

	/**
	 * 组装回程中转数据
	 * 
	 * @param table
	 * @return
	 * @throws Exception
	 */
	private List<ReturnTransitEntity> parseBackTrantsInfo(Element table) throws Exception {
		List<ReturnTransitEntity> transits = new ArrayList<ReturnTransitEntity>();
		Element planeEle = table.select("tr.result_subinfo").first(); // 航班列表
		Elements transPlanes = planeEle.select("div.airlines"); // 航班明细，多个时为有中转的
		Elements stopEles = planeEle.select("div.stopover_line"); // 中转停留时间,包括多次中转
		Element e1 = null;
		Element stop1 = null;
		String s1 = null;
		String s2 = null;
		Elements portEles = null;
		String tFlightNo = null;
		String tCarrierName = null;
		String tFlightType = null;
		String startTime = null;
		String endTime = null;
		String tFromAirPort = null;
		String tFromAirPortName = null;
		String tToAirPort = null;
		String tToAirPortName = null;
		String stayTime = null;
		ReturnTransitEntity transitEntity = null;
		try {
			for (int i = 0; i < transPlanes.size(); i++) { // 循环保存多个
				e1 = transPlanes.get(i);
				s1 = e1.getElementsByTag("dt").first().ownText().trim();
				s2 = e1.getElementsByTag("dt").first().getElementsByTag("a").first().ownText().trim();
				portEles = e1.getElementsByTag("dd");
				tFlightNo = RegHtmlUtil.regStr(s1, ".*?(\\p{Upper}{2}\\d{1,5}).*?類型");
				tCarrierName = RegHtmlUtil.regStr(s1, "(.*)\\p{Upper}{2}\\d{1,5}.*?類型"); // 解析出航空公司名称
				tFlightType = RegHtmlUtil.regStr(s1, "類型：(.*)") + s2; // 解析出机型
				startTime = RegHtmlUtil.regStr(portEles.get(0).ownText(), "週.*(\\d{2}.*月.*\\d{4}.*\\d{2}:\\d{2}).*\\p{Upper}{3}");
				endTime = RegHtmlUtil.regStr(portEles.get(portEles.size() - 1).ownText(), "週.*(\\d{2}.*月.*\\d{4}.*\\d{2}:\\d{2}).*\\p{Upper}{3}");
				tFromAirPort = RegHtmlUtil.regStr(portEles.get(0).ownText(), "\\d{2}:\\d{2}.*(\\p{Upper}{3})");
				tFromAirPortName = RegHtmlUtil.regStr(portEles.get(0).ownText(), "\\d{2}:\\d{2}.*\\p{Upper}{3}\\s*([^\\w]*)\\s*");
				tToAirPort = RegHtmlUtil.regStr(portEles.get(portEles.size() - 1).ownText(), "\\d{2}:\\d{2}.*(\\p{Upper}{3})");
				tToAirPortName = RegHtmlUtil.regStr(portEles.get(portEles.size() - 1).ownText(), "\\d{2}:\\d{2}.*\\p{Upper}{3}\\s*([^\\w]*)\\s*");
				transitEntity = PlaneInfoEntityBuilder.buildReturnTransitEntity(tFlightNo, null, null, tCarrierName, tCarrierName, tFromAirPort, tFromAirPortName, tToAirPort, tToAirPortName, tFlightType);
				transitEntity.setStartTime(XieChenFormatDataUtils.formatCNDate(startTime));
				transitEntity.setEndTime(XieChenFormatDataUtils.formatCNDate(endTime));
				if (i < transPlanes.size() - 1) { // 只在前面的中转对象里存放停留时长
					stop1 = stopEles.get(i);
					stayTime = RegHtmlUtil.regStr(stop1.getElementsByTag("p").first().ownText(), ".*\\((\\d{1,2}h:\\d{1,2}m)\\).*");
					transitEntity.setStayTime(XieChenFormatDataUtils.formatTimeInterval(stayTime));
				}
				transits.add(transitEntity);
			}
		} finally {
			planeEle = null;
			transPlanes = null;
			stopEles = null;
			e1 = null;
			stop1 = null;
			s1 = null;
			s2 = null;
			portEles = null;
			tFlightNo = null;
			tCarrierName = null;
			tFlightType = null;
			startTime = null;
			endTime = null;
			tFromAirPort = null;
			tFromAirPortName = null;
			tToAirPort = null;
			tToAirPortName = null;
			stayTime = null;
			transitEntity = null;
		}
		return transits;
	}

	/**
	 * 经停,从中转中解析出来
	 * 
	 * @param tFlightNo
	 * @param portEles
	 * @return
	 */
	private List<ReturnStopOverEntity> parseReturnStopOver(Element table) {
		List<ReturnStopOverEntity> stopOvers = new ArrayList<ReturnStopOverEntity>();
		Element planeEle = table.select("tr.result_subinfo").first(); // 航班列表
		Elements stopPlanes = planeEle.select("dd.airline_stops"); // 航班明细，多个时为有中转的

		if (stopPlanes == null || stopPlanes.isEmpty()) return stopOvers;

		Elements transPlanes = planeEle.select("div.airlines"); // 航班明细，多个时为有中转的
		Element e1 = null;
		String s1 = null;
		Elements portEles = null;
		String tFlightNo = null;
		ReturnStopOverEntity so = null;
		try {
			for (int i = 0; i < transPlanes.size(); i++) { // 循环保存多个
				e1 = transPlanes.get(i);
				s1 = e1.getElementsByTag("dt").first().ownText().trim();
				portEles = e1.getElementsByTag("dd");
				tFlightNo = RegHtmlUtil.regStr(s1, "公司.*?(\\p{Upper}{2}\\d{1,5})");
				for (Element e : portEles) {
					if (e.hasClass("airline_stops")) {// 是经停的
						so = new ReturnStopOverEntity();
						so.setFlightNo(tFlightNo);
						so.setStopCity(RegHtmlUtil.regStr(e.ownText(), "經停/中轉:  (.*)Stay time").trim());
						so.setStayTime(XieChenFormatDataUtils.formatTimeInterval(RegHtmlUtil.regStr(e.ownText(), "Stay time :  \\((\\d{1,2}h\\d{1,2}m)\\)")));
						stopOvers.add(so);
					}
				}
			}
		} finally {
			e1 = null;
			s1 = null;
			portEles = null;
			tFlightNo = null;
			so = null;
		}
		return stopOvers;
	}

	/**
	 * 获得往返程的打包价格
	 * 
	 * @return
	 */
	private Double getSumPrice(Element table) {
		String price = table.select("tr.result_class").first().select("strong.number").first().ownText();
		return NumberUtils.createDouble(price.replace(",", ""));
	}

	/**
	 * 获得返程的数据,返程暂不分页
	 * 
	 * @param table
	 * @return
	 */
	@SuppressWarnings("deprecation")
	private List<ReturnDoublePlaneInfoEntity> fetchReturnData(Element goTable, String cabinType) {
		// 统一控制变量
		if (!is_secondLayer_fetch) return null;
		logger.info(String.format("开始获取的返程航班"));
		List<ReturnDoublePlaneInfoEntity> results = new ArrayList<ReturnDoublePlaneInfoEntity>();
		HttpPost httpPost = null;
		String roundHtml = null;
		Document doc = null;
		Elements tables = null;
		String startTime = null;
		String endTime = null;
		List<ReturnTransitEntity> transits = null;
		List<ReturnStopOverEntity> stopOvers = null;
		ReturnDoublePlaneInfoEntity returnDoublePlaneinfo = null;
		for (int i = 0; i < secondLayer_fail_retry_times; i++) { // fail_retry_times次获取不成功，则退出
			try {
				httpPost = this.getFirstYudingPageUrl(goTable, cabinType);
				roundHtml = adapter.excuteRequest(httpClient, httpPost, true);
				adapter.appendPageContents(roundHtml);//设置源网页数据
				this.validateHtml(roundHtml);
				// IOUtils.write(roundHtml, new FileOutputStream(new File("E:\\mytemp\\xiechen.html")));
				doc = Jsoup.parse(roundHtml);
				tables = doc.select("div.result_list").first().children();
				logger.info(String.format("获得航班[%s]的返程航班数量：%s", this.getYudingButtonFlightNo(goTable), tables.size()));
				for (Element table : tables) {
					// 取航班明细，多个时为有中转的情况
					transits = this.parseBackTrantsInfo(table);
					startTime = RegHtmlUtil.regStr(table.getElementsByClass("column02").first().html(), "<em>(.*?)</em>").replaceAll("\\s", "");
					endTime = RegHtmlUtil.regStr(table.getElementsByClass("column02").first().html(), "<em>.*?<em>(.*?)</em>").replaceAll("\\s", "");
					returnDoublePlaneinfo = PlaneInfoEntityBuilder.buildPlaneInfo(taskQueue, null, transits.get(0).getCarrierName(), transits.get(0).getCarrierFullName(), startTime, endTime, transits.get(0).getFlightNo(), null, null, null, transits
							.get(0).getFlightType(), ReturnDoublePlaneInfoEntity.class);
					if (transits.size() > 1) {// 是有中转的
						returnDoublePlaneinfo.setReturnTransits((ArrayList) transits);
					}
					// 经停
					stopOvers = this.parseReturnStopOver(table);
					if (stopOvers != null && !stopOvers.isEmpty()) returnDoublePlaneinfo.getReturnStopOvers().addAll(stopOvers);
					// 取去程舱位，因为页面上没有具体的舱位其它信息，这里只保存一个名称
					returnDoublePlaneinfo.getReturnCabins().add(PlaneInfoEntityBuilder.buildCabinInfo(cabinParams.get(cabinType), null, null, null, null, null, null, null, ReturnCabinEntity.class));
					// 设置最低价
					returnDoublePlaneinfo.setSumLowestPrice(this.getSumPrice(table));

					// 获取更加详细的数据,点击预订按钮
					// 4、循环返程航班列表，一个一个点预订，取得航班明细
					this.fetchMoreInfoDetail(table, returnDoublePlaneinfo);

					results.add(returnDoublePlaneinfo);
				}
				break; // 成功后，即要退出循环
			} catch (Exception e) {
				if (e instanceof FlightInfoNotFoundException) break;
				logger.error(e.getMessage());
				e.printStackTrace();
				adapter.switchProxyipByHttClient(); // 抛出异常则切换IP
			} finally {
				httpPost = null;
				roundHtml = null;
				doc = null;
				tables = null;
				startTime = null;
				endTime = null;
				transits = null;
				returnDoublePlaneinfo = null;
			}
		}
		return results;
	}

	/**
	 * 抓取返程列表后的某一个返程航班的明细，点击“预订按钮”进入明细页，上面会有税费等信息
	 * 
	 * @param returnDoublePlaneInfo
	 * @throws Exception
	 */
	private void fetchMoreInfoDetail(Element backTable, ReturnDoublePlaneInfoEntity returnDoublePlaneInfo) throws Exception {
		// 统一控制
		if (!is_thirdLayer_fetch) return;
		HttpPost httpPost = null;
		Document doc = null;
		String detailHtml = null;
		Element priceDetail = null;
		Element priceEle = null;
		Element taxesPriceEle = null;
		Element totalPriceEle = null;
		String price = null, taxesPrice = null, totalPrice = null;
		for (int i = 0; i < thirdLayer_fail_retry_times; i++) {
			try {
				httpPost = this.getSecondYudingPageUrl(backTable);
				detailHtml = adapter.excuteRequest(this.httpClient, httpPost, true);
				adapter.appendPageContents(detailHtml);//设置源网页数据
				this.validateHtml(detailHtml);
				doc = Jsoup.parse(detailHtml);
				priceDetail = doc.select("div.prices_detail").first();
				priceEle = priceDetail.select("div.detail_list").get(0);
				taxesPriceEle = priceDetail.select("div.detail_list").get(1);
				totalPriceEle = priceDetail.getElementById("tprice");
				price = priceEle.getElementsByTag("b").first().ownText();
				taxesPrice = taxesPriceEle.getElementsByTag("b").first().ownText();
				totalPrice = totalPriceEle.ownText();

				returnDoublePlaneInfo.setSumLowestPrice(XieChenFormatDataUtils.formatPrice(totalPrice));
				returnDoublePlaneInfo.setTotalLowestPrice(XieChenFormatDataUtils.formatPrice(price));
				returnDoublePlaneInfo.setTotalLowestTaxesPrice(XieChenFormatDataUtils.formatPrice(taxesPrice));

				// 成功则跳出循环
				break;
			} catch (Exception e) {
				logger.error(e.getMessage());
				adapter.switchProxyipByHttClient();// 失败则进行ip的切换
			} finally {
				httpPost = null;
				doc = null;
				detailHtml = null;
				priceDetail = null;
				priceEle = null;
				taxesPriceEle = null;
				totalPriceEle = null;
				price = null;
				taxesPrice = null;
				totalPrice = null;
			}
		}
	}

	private String getNextPageUrl(Document doc) {
		if (doc.getElementsByClass("pager") != null) {
			Element ele = doc.getElementsByClass("pager").last();
			Element aele = ele.getElementsByTag("a").last();
			return aele.attr("href");
		}
		return null;
	}

	/**
	 * 获得去程页面上的预订按钮URL
	 * 
	 * @param table
	 * @return
	 * @throws Exception
	 */
	private HttpPost getFirstYudingPageUrl(Element table, String cabinType) throws Exception {
		String key = null;
		StringBuilder url = new StringBuilder("http://www.ctrip.com.hk/flights/ShowFareNext");
		List<NameValuePair> nvps = new ArrayList<NameValuePair>();
		HttpPost post = null;
		try {

			key = this.getYudingButtonFlightNo(table);
			nvps.add(new BasicNameValuePair("adult_num", "1"));
			nvps.add(new BasicNameValuePair("children_num", "0"));
			nvps.add(new BasicNameValuePair("infant_num", "0"));
			nvps.add(new BasicNameValuePair("GoogleCityName", ""));
			nvps.add(new BasicNameValuePair("classType", cabinType));
			nvps.add(new BasicNameValuePair("passengerType", "ADT"));
			nvps.add(new BasicNameValuePair("MultDcity0", taskQueue.getFromCity()));
			nvps.add(new BasicNameValuePair("MultAcity0", taskQueue.getToCity()));
			nvps.add(new BasicNameValuePair("MultDDate0", taskQueue.getFlightDate()));
			nvps.add(new BasicNameValuePair("MultDcity1", ""));
			nvps.add(new BasicNameValuePair("MultAcity1", ""));
			nvps.add(new BasicNameValuePair("MultDDate1", ""));
			nvps.add(new BasicNameValuePair("MultDcity2", ""));
			nvps.add(new BasicNameValuePair("MultAcity2", ""));
			nvps.add(new BasicNameValuePair("MultDDate2", ""));
			nvps.add(new BasicNameValuePair("MultDcity3", ""));
			nvps.add(new BasicNameValuePair("MultAcity3", ""));
			nvps.add(new BasicNameValuePair("MultDDate3", ""));
			nvps.add(new BasicNameValuePair("MultDcity4", ""));
			nvps.add(new BasicNameValuePair("MultAcity4", ""));
			nvps.add(new BasicNameValuePair("MultDDate4", ""));
			nvps.add(new BasicNameValuePair("MultDcity5", ""));
			nvps.add(new BasicNameValuePair("MultAcity5", ""));
			nvps.add(new BasicNameValuePair("MultDDate5", ""));
			nvps.add(new BasicNameValuePair("Search_FlightKey", "@" + key));
			nvps.add(new BasicNameValuePair("FlightWay", "RT"));
			nvps.add(new BasicNameValuePair("DSeatClass", cabinType));
			nvps.add(new BasicNameValuePair("DSeatSelect", cabinType));
			nvps.add(new BasicNameValuePair("ChildType", "ADT"));
			nvps.add(new BasicNameValuePair("Quantity", "1"));
			nvps.add(new BasicNameValuePair("ChildQty", "0"));
			nvps.add(new BasicNameValuePair("BabyQty", "0"));
			nvps.add(new BasicNameValuePair("AirlineChoice", ""));
			nvps.add(new BasicNameValuePair("HomePortCode", ""));
			nvps.add(new BasicNameValuePair("Dest1PortCode", ""));
			nvps.add(new BasicNameValuePair("CurrentSeqNO", "2"));
			nvps.add(new BasicNameValuePair("DCity", taskQueue.getFromCity()));
			nvps.add(new BasicNameValuePair("ACity", taskQueue.getToCity()));
			nvps.add(new BasicNameValuePair("DDatePeriod1", taskQueue.getFlightDate()));
			nvps.add(new BasicNameValuePair("ADatePeriod1", taskQueue.getReturnGrabDate()));
			nvps.add(new BasicNameValuePair("sort", "PRICE"));
			nvps.add(new BasicNameValuePair("filter_ddate", ""));
			nvps.add(new BasicNameValuePair("filter_adate", ""));
			nvps.add(new BasicNameValuePair("ptype", "ADT"));
			nvps.add(new BasicNameValuePair("pageno", "1"));
			nvps.add(new BasicNameValuePair("today", DateUtil.formatDay(new Date(), "yyyy-MM-dd")));
			post = new HttpPost(url.toString());
			this.setHKBackRequestHeadInfo(post);
			this.setHKRequestCookieInfo(post);
			post.setEntity(new UrlEncodedFormEntity(nvps));
			logger.info(String.format("组装出的去程航班预订按钮URL：%s, flightNo:%s", post.getURI().toURL(), key));
			return post;
		} finally {
			key = null;
			nvps = null;
			url = null;
		}
	}

	/**
	 * 获得预订按钮上的航班信息
	 * 
	 * @param table
	 * @return
	 */
	private String getYudingButtonFlightNo(Element table) {
		Element button = table.select("button.btn_key").first();// 返程信息点击按钮
		return RegHtmlUtil.regStr(button.attr("onclick"), "~(.*?)~");
	}

	/**
	 * 返程航班列表上的预订按钮URL
	 * 
	 * @param table
	 * @return
	 * @throws Exception
	 */
	private HttpPost getSecondYudingPageUrl(Element table) throws Exception {
		String key = null;
		StringBuilder url = new StringBuilder("http://www.ctrip.com.hk/flights/Passenger");
		List<NameValuePair> nvps = new ArrayList<NameValuePair>();
		try {
			// TODO 这里获取数据可能有问题
			key = this.getYudingButtonFlightNo(table);
			for (String k : this.queryParams.keySet()) {
				if (!k.equals("Search_FlightKey")) {
					nvps.add(new BasicNameValuePair(k, this.queryParams.get(k)));
					// System.out.print("&"+k+"="+this.queryParams.get(k));
				}
			}
			// System.out.println("&Search_FlightKey=@"+key);
			nvps.add(new BasicNameValuePair("Search_FlightKey", "@" + key));
			HttpPost post = new HttpPost(url.toString());
			post.setEntity(new UrlEncodedFormEntity(nvps));
			logger.info(String.format("返程航班列表上的预订按钮URL：%s, flightNo:%s", post.getURI().toURL(), key));
			return post;
		} finally {
			key = null;
			url = null;
			nvps = null;
		}

	}

	/**
	 * 构建公共的查询条件
	 * 
	 * @param f
	 * @throws Exception
	 */
	private void buildFormParams(Element f) throws Exception {
		Elements es = f.select("*[name]");
		for (Element e : es) {
			if ("SELECT".equals(e.tagName().toUpperCase())) {
				queryParams.put(e.attr("name"), e.select("option[selected]").attr("value"));
			} else {
				queryParams.put(e.attr("name"), e.val());
			}
		}
	}

	// 获得舱位的数据
	private List<DoublePlaneInfoEntity> fetchSingleCabinType(String cabinType) throws Exception {
		String html = null;
		String url = null;
		try {
			// 通过页面请求组装成请求的URL字符串
			url = buildEnterUrl() + "&DSeatSelect=" + cabinType;
			logger.info(String.format("获得舱位[%s]的数据URL[%s]", cabinType, url));
			html = this.requestHtmlPage(url);
			adapter.appendPageContents(html);
			return this.parseGoFlightListData(html, cabinType);
		} finally {
			url = null;
			html = null;
		}
	}

	/**
	 * 通过URL调用适配器通用请求方法，取得响应页面集
	 * 
	 * @param url
	 * @return
	 * @throws Exception
	 */
	private String requestHtmlPage(String url) throws Exception {
		HttpGet g = null;
		String html = null;
		try {
			// 通过页面请求组装成请求的URL字符串
			g = new HttpGet(url);
			setHKRequestHeadInfo(g);
			setHKRequestCookieInfo(g);

			html = adapter.excuteRequest(httpClient, g, true);
			this.validateHtml(html);
		} finally {
			g = null;
			url = null;
		}
		return html;
	}

	/**
	 * 组装去程模型
	 * 
	 * @param html
	 * @param cabinType
	 * @return
	 * @throws Exception
	 */
	private List<DoublePlaneInfoEntity> parseGoFlightListData(String html, String cabinType) throws Exception {
		List<DoublePlaneInfoEntity> results = new ArrayList<DoublePlaneInfoEntity>();
		Document doc = null;
		// 保证第一个页面的数据正确入库,解析出数据
		DoublePlaneInfoEntity doublePlaneinfo = null;
		String nextPageUrl = null;
		String startTime = null;
		String endTime = null;
		String stayTime = null;
		List<TransitEntity> transits = null;
		List<StopOverEntity> stopOvers = null;
		Elements tables = null;
		List<ReturnDoublePlaneInfoEntity> returnList = null;
		try {
			// 根据三个舱位信息请求三次,经济Y，商务C，头等F ,如果三个舱位信息都没有航班数据，则返回没有航班异常
			// 1、根据查询条件直接进入到去程航班列表(默认是经济舱的,代码是Y)
			// 2、循环取多页
			doc = Jsoup.parse(html);
			if (this.queryParams.isEmpty()) this.buildFormParams(doc); // 因为会用到公共的查询条件，所以这个单独放置出来

			tables = doc.select("div.result_list").first().children();
			logger.info(String.format("获得的去程航班数量：%s", tables.size()));
			for (Element table : tables) {
				try {
					logger.info(String.format("获得的去程航班[%s]", this.getYudingButtonFlightNo(table)));
					// 取航班明细，多个时为有中转的情况
					transits = this.parseGoTrantsInfo(table);
					startTime = RegHtmlUtil.regStr(table.getElementsByClass("column02").first().html(), "<em>(.*?)</em>").replaceAll("\\s", "");
					endTime = RegHtmlUtil.regStr(table.getElementsByClass("column02").first().html(), "<em>.*?<em>(.*?)</em>").replaceAll("\\s", "");
					stayTime = RegHtmlUtil.regStr(table.getElementsByClass("column04").first().html(), ".*<p>(\\d{1,2}h\\d{1,2}m)</p>");
					logger.info("startTime=" + startTime + ";endTime=" + endTime + ";stayTime=" + stayTime + ";transits.get(0).getFlightNo()=" + transits.get(0).getFlightNo() + ";transits.get(0).getCarrierName()=" + transits.get(0).getCarrierName());
					doublePlaneinfo = PlaneInfoEntityBuilder.buildPlaneInfo(taskQueue, null, transits.get(0).getCarrierName(), transits.get(0).getCarrierFullName(), startTime, endTime, transits.get(0).getFlightNo(), null, null, null, transits.get(0)
							.getFlightType(), DoublePlaneInfoEntity.class);
					if (transits.size() > 1) {// 是有中转的
						doublePlaneinfo.setTransits((ArrayList) transits);
					}
					// 经停
					stopOvers = this.parseStopOver(table);
					if (stopOvers != null && !stopOvers.isEmpty()) doublePlaneinfo.getStopOvers().addAll(stopOvers);
					// 取去程舱位，因为页面上没有具体的舱位其它信息，这里只保存一个名称
					doublePlaneinfo.getCabins().add(PlaneInfoEntityBuilder.buildCabinInfo(cabinParams.get(cabinType), null, null, null, null, null, null, null, CabinEntity.class));
					doublePlaneinfo.setFlightDuration(XieChenFormatDataUtils.formatTimeInterval(stayTime));
					// 设置最低价
					doublePlaneinfo.setSumLowestPrice(this.getSumPrice(table));
					// 获取返程数据,点击预订按钮
					// 3、循环去程航班列表，一个一个取航班返程（点预订按钮）,得到的是一个对应于去程航班的返程航班列表
					returnList = this.fetchReturnData(table, cabinType);
					if (returnList != null && !returnList.isEmpty()) doublePlaneinfo.getReturnPlaneInfos().addAll(returnList);

					this.compareHightestLowestPrice(doublePlaneinfo);
					// System.out.println(doublePlaneinfo.getStartTime()+"=="+doublePlaneinfo.getEndTime()+"=="+doublePlaneinfo.getFlightNo());
					results.add(doublePlaneinfo);
				} finally {
					transits = null;
					startTime = null;
					endTime = null;
					stayTime = null;
					doublePlaneinfo = null;
					returnList = null;
				}
			}
			if (is_pageno_fetch) {// 抓取分页开关开启
				Thread.sleep(1000);// 每翻一页，休眠一下
				nextPageUrl = this.getNextPageUrl(doc);
				HttpGet httpGet = null;
				String pageNoHtml = null;
				if (StringUtils.isNotBlank(nextPageUrl)) {// 到达最后一页时，下一页按钮url会为空的
					for (int i = 0; i < pageno_fail_retry_times; i++) {
						try {
							// 请求分页URL，获得html页面
							httpGet = new HttpGet(nextPageUrl);
							pageNoHtml = adapter.excuteRequest(httpClient, httpGet, true);
							this.validateHtml(pageNoHtml);
							// 传递给解析去程方法，重复解析步骤1，2，3
							results.addAll(this.parseGoFlightListData(pageNoHtml, cabinType));
							break;// 成功的话，退出循环
						} catch (Exception e) {
							logger.error(e.getMessage());
							adapter.switchProxyipByHttClient(); // 切换IP
						} finally {
							httpGet = null;
							pageNoHtml = null;
						}
					}
				}
			}
		} finally {
			doc = null;
			doublePlaneinfo = null;
			nextPageUrl = null;
			startTime = null;
			endTime = null;
			stayTime = null;
			transits = null;
			tables = null;
		}
		return results;
	}

	/**
	 * 验证抓取到的html是否有效
	 * 
	 * @param html
	 * @throws Exception
	 */
	private void validateHtml(String html) throws Exception {
		if (html == null) {
			throw new SourceWebPageNullException("网页数据为空");
		}
		if (html.length() < 5000) {
			throw new SourceDataParseNullException("网页数据错误!");
		}
		html = html.replaceAll("\\s", "");
		if (html.matches(".*抱歉，無符合搜尋條件的航班.*")) {
			throw new FlightInfoNotFoundException();
		}
		if (!html.matches(".*class=\"result_list\".*")) {
			throw new SourceDataParseNullException("网页没有结果集");
		}
	}

	/**
	 * 构建需要的请求URL
	 */
	private String buildEnterUrl() throws Exception {
		StringBuffer bf = new StringBuffer("");
		
		bf.append("http://www.ctrip.com.hk/flights/").append(taskQueue.getFromCityName().split("\\|")[1].toLowerCase() + "-to-" + taskQueue.getToCityName().split("\\|")[1].toLowerCase())
				.append("/tickets-" + taskQueue.getFromCity().toLowerCase() + "-" + taskQueue.getToCity().toLowerCase()).append("?flighttype=d").append("&dcity=" + taskQueue.getFromCity()).append("&acity=" + taskQueue.getToCity())
				.append("&relddate=" + this.getGapDay(taskQueue.getFlightDate())).append("&relrdate=" + this.getGapDay(taskQueue.getReturnGrabDate())).append("&startdate=" + taskQueue.getFlightDate())
				.append("&returndate=" + taskQueue.getReturnGrabDate()).append("&startday=" + XieChenFormatDataUtils.getWeekNameByDate(taskQueue.getFlightDate()))
				.append("&returnday=" + XieChenFormatDataUtils.getWeekNameByDate(taskQueue.getReturnGrabDate())).append("&relweek=0&searchboxArg=t");
		if (is_no_stop) bf.append("&Transfer_Type=1&transfer=1");
		return bf.toString();
	}

	/**
	 * 根据起飞时间获取与当前的间隔时间
	 * 
	 * @param flightDateStr
	 * @return
	 * @throws Exception
	 */
	private int getGapDay(String flightDateStr) throws Exception {
		long oneDay = 24 * 60 * 60 * 1000;
		Date now = new Date();
		now.setHours(0);
		now.setMinutes(0);
		now.setSeconds(0);
		SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
		Date flight = sf.parse(flightDateStr);
		long chaTime = flight.getTime() - now.getTime();
		long chaDay = chaTime / oneDay;
		chaDay = chaTime % oneDay > 0 ? chaDay + 1 : chaDay;
		return (int) chaDay;
	}

	/**
	 * 设置的请求的头部
	 */
	private void setHKRequestHeadInfo(HttpRequestBase request) throws Exception {
		Map<String, String> map = new HashMap<String, String>();
		map.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		map.put("Accept-Language", "zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3");
		map.put("Host", "www.ctrip.com.hk");
		map.put("Referer", "http://www.ctrip.com.hk/");
		map.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0");
		adapter.setRequestHead(request, map);
	}

	/**
	 * 设置返程的请求的头部
	 */
	private void setHKBackRequestHeadInfo(HttpRequestBase request) throws Exception {
		Map<String, String> map = new HashMap<String, String>();
		map.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		map.put("Accept-Language", "zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3");
		map.put("Host", "www.ctrip.com.hk");
		map.put("Referer", this.buildEnterUrl());
		map.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0");
		adapter.setRequestHead(request, map);
	}

	/**
	 * 在cookie中设置币种为人民币
	 * 
	 * @param request
	 * @throws Exception
	 */
	private void setHKRequestCookieInfo(HttpRequestBase request) throws Exception {
		request.addHeader("Cookie", "cookiePricesDisplayed=CNY;");
	}

	/**
	 * 比较一下得出最低价
	 * 
	 * @param list
	 */
	private void compareHightestLowestPrice(DoublePlaneInfoEntity entity) {
		if (entity != null) {
			Double hightestPrice = 0d;
			Double lowestPrice = 10000000000d;

			if (entity.getReturnPlaneInfos() != null) {
				for (ReturnDoublePlaneInfoEntity re : entity.getReturnPlaneInfos()) {
					if (re.getSumLowestPrice() != null) {
						if (hightestPrice < re.getSumLowestPrice()) hightestPrice = re.getSumLowestPrice();
						if (lowestPrice > re.getSumLowestPrice()) lowestPrice = re.getSumLowestPrice();
					}
				}
			}
			if (hightestPrice > 0) entity.setSumHighestPrice(hightestPrice);
			if (lowestPrice < 10000000000d) entity.setSumLowestPrice(lowestPrice);
		}
	}
}
